/*
Copyright (c) 2011, Stephane Albert
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the organization nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL STEPHANE ALBERT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef DEBRIDFETCHERBASE_H
#define DEBRIDFETCHERBASE_H

#include <iostream>
#include <stdlib.h> //Rand used for 4shared
#include <map>
#include <string>
#include <fstream>
#include <signal.h>
#include <curl/curl.h>
#include <pthread.h>
#ifdef WIN32
    #include <windows.h>
#else
    #include <unistd.h>
#endif

#include "config.h"
#include "global.h"

enum /*GET_ERR*/ {
    NO_ERR      = 0,
    URL_ERR,
    CONN_ERR,
    CNCL_ERR,
    HOST_ERR,
    SIZE_ERR,
    SYS_ERR
};

enum /*ERROR_TYPE*/ {
    MISC            = 0,    //= 0b0000
    DECODE          = 1,    //= 0b0001
    DEBRID          = 2,    //= 0b0010
    DOWNLOAD        = 4,    //= 0b0100
    MISC_ERR        = 8,    //= 0b1000
    DECODE_ERR      = 9,    //= 0b1001
    DEBRID_ERR      = 10,   //= 0b1010
    DOWNLOAD_ERR    = 12    //= 0b1100
};

enum /*LOG_LEVEL*/ {
    NO_LOG      = 0,        //Default no logging
    LFFAIL_LOG  = 1,        //Log failed to faillog
    CFAIL_LOG   = 2,        //Log only failed to console
    CMSG_LOG    = 4,        //Log to console (msg)
    CALL_LOG    = 6,        //Log to console (msg & failed)
    FFAIL_LOG   = 8,        //Log only failed to file
    FMSG_LOG    = 16,       //Log to file (msg)
    FALL_LOG    = 24,       //Log to file (msg & failed)
    ALL_LOG     = 31,       //Log to file (msg & failed)
};

class debridFetcherBase;

struct fetchFileStruct {
    debridFetcherBase *fetcher;
    std::ofstream * file;
}; typedef struct fetchFileStruct fetchFileStruct;

class debridFetcherBase {
    public:
        debridFetcherBase();
        virtual ~debridFetcherBase();
        /*
         * General setup functions
         */
        void setMaxRetries(int newMaxRetries);
        int getMaxRetries() const;
        /*
         * File setup functions
         */
        void setLogFile(const std::string& newLogFile);
        void setFailedLogFile(const std::string& newFailedLogFile);
        /*
         * Directories setup functions
         */
        void setMonitorDir(const std::string& newMonitorDir);
        std::string getMonitorDir() const;
        void setWorkingDir(const std::string& newWorkingDir);
        std::string getWorkingDir() const;
        void setDoneDir(const std::string& newDoneDir);
        std::string getDoneDir() const;
        /*
         * Logging functions
         */
        /**
        * \brief Sets the log level.
        * \param newLogLevel The new log level.
        * Sets a new log level, you can use an association of every LOG_LEVEL,
        * if the number is negative log is disabled.
        */
        void setLogLevel(int newLogLevel);
        /**
        * \brief This function is used to log a message.
        * \param msg Message to log.
        * \param flags Error type.
        * This function will never log to logFailedFile because we can't find the link.
        */
        void logMessage(const std::string& msg, int flags = MISC);
        /**
        * \brief This function is used to log a message.
        * \param msg Message to log.
        * \param linkURL URL to log to logFailedFile.
        * \param flags Error type.
        * This function will log msg and failed link to logFailedFile.
        */
        void logMessage(const std::string& msg, const std::string& linkURL, int flags = MISC);
        /*
         * cURL low-level I/O functions
         */
        static size_t dataToStr(void *rawBuffer, size_t size, size_t nmemb, void *strPtr);
        static size_t dataToFile(void *rawBuffer, size_t size, size_t nmemb, void *filePtr);
        static size_t dataToFileFFS(void *rawBuffer, size_t size, size_t nmemb, void *ffs_ptr);
        static int seekFile(void *instream, curl_off_t offset, int origin);
        /*
         *  Online service related functions
         */
        /**
        * \brief Function used to authenticate the client with the service's server.
        * \param username Username used to authenticate the client.
        * \param password Password used to authenticate the client.
        * \return True if authentication is successful, if it fails it'll return false.
        */
        virtual bool authClient(const std::string& username, const std::string& password) = 0;
        /**
        * \brief Function used to request a link from the service's server.
        * \param linkURL The raw link from the host which you want to get.
        * \return A new link pointing to the service's server, or an error message if it failed.
        */
        virtual std::string debridLink(const std::string &linkURL) = 0;
        /**
        * \brief Function used to logout from the service and free the session token.
        */
        virtual void logout() = 0;
        /*
         * Internal functions used to process URL and extract data, or convert them.
         */
        /**
        * \brief Function used to decode a raw link into a list of links or a valid link to debrid.
        * \param linkURL The raw link from the host you want to get.
        * \return A new link pointing to the service's server, or an error message if it failed.
        * Can alter the fileList in this object if the requested link is a directory of mutliple files,
        * you shouldn't call it outside of a debridFetcher object because fileList can be NULL. However it's still public
        * to allow a single link decoding for other purpose than downloading straight away.
        */
        std::string decodeLink(const std::string& linkURL);
        std::string urlEncode(const std::string& rawURL);
        std::string urlDecode(const std::string& rawURL);
        static void initHtmlChars();
        static std::string htmlEncode(const std::string& rawString);
        static std::string htmlDecode(const std::string& rawString);
        /*
         * Download list handling
         */
        void processDownloadList(const std::string& downloadList);
        std::stringstream *getFileContent(const std::string& filePath);
        void dlEachFile();
        /*
         * File downloading functions
         */
        /**
        * \brief Function used to download a link.
        * \param fileURL The link from the host you want to get.
        * \return An error number corresponding to the error or NO_ERR if everything went fine.
        * Returns an error number of GET_ERR type.
        */
        int getFile(const std::string& fileURL);
        /*
         * Post process and misc functions
         */
        void processFiles();
        /*
         * Thread functions
         */
        /**
        * \brief Start the thread.
        * \return True if the thread is successfuly created, false if it failed.
        */
        bool start();
        /**
        * \brief Cancel the current download.
        */
        bool cancel();
        /**
        * \brief Stop and delete the thread.
        * \param forceExit if true the current download will be cancelled too.
        */
        void stop(bool forceExit=true);
        bool needToStop();
    protected:
        CURL *curlHandle;
        pthread_t *fetchThread;
        int logLevel;
        int maxRetries;
        bool mustExit;
        bool mustStopDL;
        static std::map<std::string, std::string> htmlChars;
        std::stringstream *fileList;
        std::string monitorDirectory;
        std::string workingDirectory;
        std::string doneDirectory;
        std::ofstream logFile;
        std::ofstream logFailedFile;
        /*
         * cURL related functions
         */
        bool initCURL();
        bool loadCURLSettings();
        void unloadCURL();
        /*
         * cURL communication functions
         */
        std::string postData(const std::string& URL, const std::string& postData);
        std::string getData(const std::string& URL, bool follow=true);
        /*
         *  Online service related functions
         */
        virtual bool isDebrid(const std::string& link) = 0;
        virtual bool checkFileForError(const std::string& filePath) = 0;
        /*
         * Folder decode function
         */
        std::string decodeWuploadFolder(const std::string& folderID);
        std::string decodeMegauploadFolder(const std::string& folderID);
        std::string decodeFileserveFolder(const std::string& folderURL);
        std::string decodeFilesonicFolder(const std::string& folderURL);
        std::string decode4sharedFolder(const std::string& folderURL);
        std::string decodeFilecrop(const std::string& linkURL);
        /*
         * File functions
         */
        /**
        * \brief Checks and removes bad files.
        * \param fileName Path to the file to test.
        * \return true if the file was bad, false if it's good.
        * The functions checks for know wrong files, and deletes it if found.
        * If a bad file is found it'll try to delete it and will return true.
        * If the file is good it'll return false.
        */
        bool checkDelBadFile(const std::string& fileName);
        /*
         * Platform dependent FS and event functions
         */
        virtual bool searchForFiles() = 0;
        virtual void watchDirectory() = 0;
        /*
         * Thread functions
         */
        bool createThread();
        static void* threadEntry(void *data);
        bool deleteThread();
        /*
         * Misc functions
         */
        /**
        * \brief Function used to insert a string inside a stringstream.
        * \param str The string to be inserted into.
        * This is basicaly used to insert newfile list into the filelist.
        */
        void insertStrInFList(const std::string& str);
};

#endif // DEBRIDFETCHERBASE_H
